﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SafeScript.MetaModel
{
    public class BuiltinType : Class
    {
        public override bool IsEqualTo(ITypeDef typedef)
        {
            return this.GetType() == typedef.GetType();
        }
    }

    public enum BuiltinFunctionEnum
    {
        Object_ToString = 1,
        String_ToLower = 2,
        String_ToUpper = 3,
        String_Substring = 4,
        String_Slice = 5,
        String_IndexOf = 6,
        String_LastIndexOf = 7,
        String_Split = 8,
        String_Search = 9,
        String_Replace = 10,
        String_Match = 11,
        String_CharCodeAt = 12,
        Strinf_FromCharCode = 13
    }

    public enum BuiltinVariableEnum
    {
        String_Length = 1
    }

    public class BuiltinMemberFunction : MemberFunction
    {
        public BuiltinFunctionEnum Id;

        public BuiltinMemberFunction(string name, BuiltinType owner, BuiltinFunctionEnum id, string code)
        {
            this.Name = name;
            this.Owner = owner;
            this.Id = id;
            this.Attributes = new MemberAttribute[] { new JSNativeFunctionAttribute() { Name = "JSNativeMember", JSCode = code, IsMember = true } };
        }

        public void AddParameter(Parameter p)
        {
            this.Parameters.Add(p);
            this.ParameterTypes.Add(p.Type);
        }
    }

    public class BuiltinMemberVariable : MemberVariable
    {
        public BuiltinVariableEnum Id;

        public BuiltinMemberVariable(string name, BuiltinType owner, BuiltinVariableEnum id, string code)
        {
            this.Name = name;
            this.Owner = owner;
            this.Id = id;
            this.Attributes = new MemberAttribute[] { new JSNativeVariableAttribute() { Name = "JSNativeMember", JSCode = code, IsMember = true } };
        }
}

    public class InstanceType : BuiltinType
    {
        public ITypeDef Type;

        public override bool IsEqualTo(ITypeDef typedef)
        {
            InstanceType t = typedef as InstanceType;
            if (t == null)
                return false;
            return (t.Type == this.Type) ;
        }

        public override bool CanBeAssignedTo(ITypeDef typedef)
        {
            InstanceType t = typedef as InstanceType;
            if (t != null)
                return this.CanBeAssignedTo(t.Type);
            return this.Type.CanBeAssignedTo(typedef);
        }

        public override int GetInheritanceDepth(ITypeDef typedef)
        {
            InstanceType t = typedef as InstanceType;
            if (t != null)
                return this.GetInheritanceDepth(t.Type);
            return this.Type.GetInheritanceDepth(typedef);
        }

        public override ITypeDef Instantiate(Dictionary<TemplateParameterType, ITypeDef> templateArguments)
        {
            ITypeDef t = this.Type.Instantiate(templateArguments);
            if (t == this.Type)
                return this;
            return new InstanceType() { Type = t };
        }
    }

    public class ObjectType : BuiltinType
    {
        private static ObjectType self;

        private ObjectType()
        {
            this.Name = "object";
            self = this;
            this.ScopeElements["ToString"] = new BuiltinMemberFunction("ToString", this, BuiltinFunctionEnum.Object_ToString, "toString") {
                ReturnType = new InstanceType() { Type = StringType.Self }
            };
        }

        public static ObjectType Self
        {
            get
            {
                if (self == null)
                    self = new ObjectType();
                return self;
            }
        }
    }

    public class StringType : BuiltinType
    {
        private static StringType self;

        private StringType()
        {
            self = this;
            this.Attributes = new MemberAttribute[] { new JSNativeClassAttribute() { Name = "String" } };
            this.Name = "string";
            this.BaseClass = ObjectType.Self;
            MemberVariable mf = new BuiltinMemberVariable("Length", this, BuiltinVariableEnum.String_Length, "length") { Type = new InstanceType() { Type = NumericType.Self } };
            this.ScopeElements[mf.Name] = mf;
            // ToLower
            BuiltinMemberFunction f = new BuiltinMemberFunction("ToLower", this, BuiltinFunctionEnum.String_ToLower, "toLowerCase");
            f.ReturnType = new InstanceType() { Type = StringType.Self };
            f.ComputeType(null);
            this.ScopeElements[f.Name] = f;
            // ToUpper
            f = new BuiltinMemberFunction("ToUpper", this, BuiltinFunctionEnum.String_ToUpper, "toUpperCase");
            f.ReturnType = new InstanceType() { Type = StringType.Self };
            f.ComputeType(null);
            this.ScopeElements[f.Name] = f;
            // Split
            f = new BuiltinMemberFunction("Split", this, BuiltinFunctionEnum.String_Split, "split");
            f.ReturnType = new InstanceType() { Type = StringType.Self };
            f.AddParameter(new Parameter() { Name = "start", Type = new InstanceType() { Type = NumericType.Self } });
            f.AddParameter(new Parameter() { Name = "end", Type = new InstanceType() { Type = NumericType.Self } });
            f.ComputeType(null);
            this.ScopeElements[f.Name] = f;
            // Substring
            f = new BuiltinMemberFunction("Substring", this, BuiltinFunctionEnum.String_Substring, "substr");
            f.ReturnType = new InstanceType() { Type = StringType.Self };
            f.AddParameter(new Parameter() { Name = "start", Type = new InstanceType() { Type = NumericType.Self } });
            f.AddParameter(new Parameter() { Name = "length", Type = new InstanceType() { Type = NumericType.Self } });
            f.ComputeType(null);
            this.ScopeElements[f.Name] = f;
            // IndexOf
            f = new BuiltinMemberFunction("IndexOf", this, BuiltinFunctionEnum.String_IndexOf, "indexOf");
            f.ReturnType = new InstanceType() { Type = NumericType.Self };
            f.AddParameter(new Parameter() { Name = "str", Type = new InstanceType() { Type = StringType.Self } });
            f.ComputeType(null);
            this.ScopeElements[f.Name] = f;
            // LastIndexOf
            f = new BuiltinMemberFunction("LastIndexOf", this, BuiltinFunctionEnum.String_IndexOf, "lastIndexOf");
            f.ReturnType = new InstanceType() { Type = NumericType.Self };
            f.AddParameter(new Parameter() { Name = "str", Type = new InstanceType() { Type = StringType.Self } });
            f.ComputeType(null);
            this.ScopeElements[f.Name] = f;
            // Split
            MemberFunctionSet fs = new MemberFunctionSet() { Name = "Split" };
            this.ScopeElements[fs.Name] = fs;
            f = new BuiltinMemberFunction("Split", this, BuiltinFunctionEnum.String_IndexOf, "split");
            f.ReturnType = new InstanceType() { Type = ListType.Instantiate(StringType.Self) };
            f.AddParameter(new Parameter() { Name = "seperator", Type = new InstanceType() { Type = StringType.Self } });
            f.ComputeType(null);
            fs.Add(f, null);
            this.ScopeElements[fs.Name] = fs;
            f = new BuiltinMemberFunction("Split", this, BuiltinFunctionEnum.String_IndexOf, "split");
            f.ReturnType = new InstanceType() { Type = ListType.Instantiate(StringType.Self) };
            f.AddParameter(new Parameter() { Name = "seperator", Type = new InstanceType() { Type = StringType.Self } });
            f.AddParameter(new Parameter() { Name = "max", Type = new InstanceType() { Type = NumericType.Self } });
            f.ComputeType(null);
            fs.Add(f, null);
            // CharCodeAt
            f = new BuiltinMemberFunction("CharCodeAt", this, BuiltinFunctionEnum.String_IndexOf, "charCodeAt");
            f.ReturnType = new InstanceType() { Type = NumericType.Self };
            f.AddParameter(new Parameter() { Name = "pos", Type = new InstanceType() { Type = NumericType.Self } });
            f.ComputeType(null);
            this.ScopeElements[f.Name] = f;
            // LastIndexOf
            f = new BuiltinMemberFunction("FromCharCode", this, BuiltinFunctionEnum.String_IndexOf, "String.fromCharCode");
            f.ReturnType = new InstanceType() { Type = StringType.Self };
            f.AddParameter(new Parameter() { Name = "code", Type = new InstanceType() { Type = NumericType.Self } });
            f.ComputeType(null);
            f.IsStatic = true;
            this.ScopeElements[f.Name] = f;
        }

        public static StringType Self
        {
            get
            {
                if (self == null)
                    self = new StringType();
                return self;
            }
        }
    }

    public class NumericType : BuiltinType
    {
        private static NumericType self;

        private NumericType()
        {
            this.Name = "num";
            this.BaseClass = ObjectType.Self;
        }

        public static NumericType Self
        {
            get
            {
                if (self == null)
                    self = new NumericType();
                return self;
            }
        }
    }

    public class BoolType : BuiltinType
    {
        private static BoolType self;

        private BoolType()
        {
            this.Name = "bool";
            this.BaseClass = ObjectType.Self;
        }

        public static BoolType Self
        {
            get
            {
                if (self == null)
                    self = new BoolType();
                return self;
            }
        }
    }

    public class VoidType : BuiltinType
    {
        private static VoidType self;

        private VoidType()
        {
            this.Name = "void";
        }

        public static VoidType Self
        {
            get
            {
                if (self == null)
                    self = new VoidType();
                return self;
            }
        }
    }

    public class NullType : BuiltinType
    {
        private static NullType self;

        private NullType()
        {
            this.Name = "null";
        }

        public static NullType Self
        {
            get
            {
                if (self == null)
                    self = new NullType();
                return self;
            }
        }

        public override bool CanBeAssignedTo(ITypeDef typedef)
        {
            // Everything is an object and everything can become null
            return true;
        }
    }

    public class ListType : BuiltinType
    {
        private static ListType self;

        private ListType()
        {
            this.Name = "list#1";
            this.BaseClass = ObjectType.Self;
            var t = new TemplateParameterType() { Name = "T", ParentScope = this };
            // Add a template parameter
            this.TemplateParameters.Add(t);
            this.TemplateParameters[0].BaseTypes.Add(ObjectType.Self);

            BuiltinMemberFunction f = new BuiltinMemberFunction("Push", this, BuiltinFunctionEnum.String_ToLower, "push");
            f.AddParameter(new Parameter() { Name = "value", Type = new InstanceType() { Type = this.TemplateParameters[0] } });
            f.ReturnType = VoidType.Self;
            f.ComputeType(null);
            this.ScopeElements[f.Name] = f;
            MemberVariable mf = new BuiltinMemberVariable("Length", this, BuiltinVariableEnum.String_Length, "length") { Type = new InstanceType() { Type = NumericType.Self } };
            this.ScopeElements[mf.Name] = mf;
        }

        public static ListType Self
        {
            get
            {
                if (self == null)
                    self = new ListType();
                return self;
            }
        }

        public static ITypeDef Instantiate(ITypeDef type)
        {
            Dictionary<TemplateParameterType,ITypeDef> typeArguments = new Dictionary<TemplateParameterType,ITypeDef>();
            typeArguments[ListType.Self.TemplateParameters[0]] = type;
            return Compiler.CurrentCompiler.GetTemplateInstance(ListType.Self, typeArguments);
        }
    }

    public class DictType : BuiltinType
    {
        private static DictType self;

        private DictType()
        {
            this.Name = "dict#2";
            this.BaseClass = ObjectType.Self;
            // Add a template parameter
            this.TemplateParameters.Add(new TemplateParameterType() { Name = "K", ParentScope = this });
            this.TemplateParameters.Add(new TemplateParameterType() { Name = "V", ParentScope = this });
            this.TemplateParameters[0].BaseTypes.Add(ObjectType.Self);
            this.TemplateParameters[1].BaseTypes.Add(ObjectType.Self);
        }

        public static DictType Self
        {
            get
            {
                if (self == null)
                    self = new DictType();
                return self;
            }
        }

        public static ITypeDef Instantiate(ITypeDef keyType, ITypeDef valueType)
        {
            Dictionary<TemplateParameterType, ITypeDef> typeArguments = new Dictionary<TemplateParameterType, ITypeDef>();
            typeArguments[DictType.Self.TemplateParameters[0]] = keyType;
            typeArguments[DictType.Self.TemplateParameters[1]] = valueType;
            return Compiler.CurrentCompiler.GetTemplateInstance(DictType.Self, typeArguments);
        }
    }
}
